package vip.liux.contracts.security.externalLogin;

import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.springframework.context.ApplicationContext;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import vip.liux.contracts.models.identity.IdentityUser;
import vip.liux.contracts.models.identity.UserDomainManager;
import vip.liux.contracts.models.identity.vo.UserLoginInfo;
import vip.liux.contracts.security.AnonymousUserDetails;
import vip.liux.contracts.security.UserDetailsManagerAdapter;
import vip.liux.contracts.security.externalLogin.authenticator.ExternalLoginAuthenticator;
import vip.liux.contracts.security.externalLogin.authenticator.ExternalLoginProvider;

import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;

import static org.slf4j.LoggerFactory.getLogger;
import static vip.liux.contracts.security.AuthenticationKit.getCredentialsStr;
import static vip.liux.contracts.security.AuthenticationKit.getPrincipalStr;
import static vip.liux.contracts.security.UserDetailsManagerAdapter.UserConverter.toIdentityUser;

/**
 * 外部登录认证的 Provider，负责对接第三方（如微信、Google、Facebook 等）认证流程。
 * 支持多种外部认证方式的适配与认证扩展。
 */
@Setter
@Getter
public class ExternalLoginAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {

    private static final Logger log = getLogger(ExternalLoginAuthenticationProvider.class);
    /**
     * 已注册的外部认证器，key 为 loginProvider 字符串，value 为对应的认证器实现
     */
    private final Map<String, ExternalLoginAuthenticator> authenticators = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
    /**
     * 用户领域服务（用于持久化、查找用户信息）
     */
    private UserDomainManager userDomainManager;
    /**
     * 密码加密器（用于密码校验/加密）
     */
    private PasswordEncoder passwordEncoder;
    /**
     * Spring上下文（用于自动装配所有 ExternalLoginAuthenticator 实现）
     */
    private ApplicationContext applicationContext;

    /**
     * 自动注册所有ExternalLoginAuthenticator实现。
     * 通过Spring容器自动注入所有 ExternalLoginAuthenticator，并按其supports类型注册到 authenticators Map中。
     */
    @Override
    public void doAfterPropertiesSet() {
        if (this.applicationContext != null) {
            Map<String, ExternalLoginAuthenticator> beans = applicationContext.getBeansOfType(ExternalLoginAuthenticator.class);
            for (ExternalLoginAuthenticator authenticator : beans.values()) {
                for (ExternalLoginProvider provider : ExternalLoginProvider.values()) {
                    if (authenticator.supports(provider)) {
                        String key = provider.name();
                        if (authenticators.containsKey(key)) {
                            log.warn("Provider [{}] already registered. Overriding with [{}]", key, authenticator.getClass().getName());
                        }
                        authenticators.put(key, authenticator);
                    }
                }
            }
        }
    }

    /**
     * 额外的认证检查。
     * 主要用来处理外部认证逻辑，如根据 loginProvider 选择对应的 ExternalLoginAuthenticator 进行认证。
     */
    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        // 如果用户信息是 User 类型，说明是未找到系统用户, 需要进行外部认证,
        if (userDetails instanceof AnonymousUserDetails) {
            String loginProvider = getPrincipalStr(authentication).orElse("");
            if (StringUtils.isBlank(loginProvider)) {
                log.warn("loginProvider 参数为空");
                throw new BadCredentialsException("认证参数[loginProvider]不能为空");
            }
            ExternalLoginAuthenticator authenticator = authenticators.get(loginProvider);
            if (authenticator == null) {
                log.warn("不支持的认证类型: {}", loginProvider);
                throw new UnsupportedProviderException("不支持的认证类型: " + loginProvider);
            }
            try {
                String providerKey = getCredentialsStr(authentication).orElse("");
                if (StringUtils.isBlank(providerKey)) {
                    log.warn("providerKey 参数为空");
                    throw new BadCredentialsException("认证参数[providerKey]不能为空");
                }
                UserDetails authenticatedUser = authenticator.authenticate(providerKey);
                if (authenticatedUser == null) {
                    log.warn("外部认证失败 loginProvider={} providerKey={}", loginProvider, providerKey);
                    throw new ExternalServiceException("外部认证失败: " + loginProvider);
                }
                authentication.setDetails(authenticatedUser);
            } catch (ExternalServiceException e) {
                log.error("外部服务异常: {}", e.getMessage());
                throw e;
            } catch (UnsupportedProviderException e) {
                log.error("认证类型不支持: {}", e.getMessage());
                throw e;
            } catch (Exception ex) {
                log.error("外部认证器异常: {}", ex.getMessage(), ex);
                throw new ExternalServiceException("外部认证器异常: " + ex.getMessage(), ex);
            }
        }
    }

    /**
     * 根据外部登录信息检索用户信息。
     * 先查本地用户库，如果查不到则交由 additionalAuthenticationChecks 做外部认证。
     */
    @Override
    protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        String principal = getPrincipalStr(authentication).orElse("");
        String credentials = getCredentialsStr(authentication).orElse("");
        try {
            Optional<IdentityUser> optional = userDomainManager
                    .findByLogin(principal, credentials);
            if (optional.isEmpty()) {
                throw new UsernameNotFoundException(
                        "UserDetailsService returned null, which is an interface contract violation");
            }
            return UserDetailsManagerAdapter.UserConverter.toUserInfo(optional.get());
        } catch (UsernameNotFoundException ex) {
            // 用户不存在，返回无状态的 UserDetails，等待后续 additionalAuthenticationChecks 处理外部认证
            return new AnonymousUserDetails(
                    principal,
                    credentials
            );
        } catch (InternalAuthenticationServiceException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
        }
    }

    /**
     * 创建成功的认证信息。
     * 若外部认证为新用户，自动注册并回填本地用户信息。
     */
    @Override
    protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
        UserDetails effectiveUser = user;
        if (authentication.getDetails() instanceof UserDetails extUser) {
            effectiveUser = extUser;
        }
        if (StringUtils.isBlank(effectiveUser.getPassword())) {
            // todo 这里可以考虑使用随机用户名去创建用户
            IdentityUser loadedUser = userDomainManager.findByUsername(effectiveUser.getUsername());
            if (loadedUser == null) {
                loadedUser = toIdentityUser(effectiveUser, UserDetailsManagerAdapter.DEFAULT_PASSWORD, "");
                userDomainManager.createUser(loadedUser);
            }
            // todo 添加登录信息
            userDomainManager.addLogin(loadedUser, new UserLoginInfo(
                    getPrincipalStr(authentication).orElseThrow(),
                    getCredentialsStr(authentication).orElseThrow(),
                    ""
            ));
            // todo 会写用户信息到缓存中
            effectiveUser = UserDetailsManagerAdapter.UserConverter.toUserInfo(loadedUser);
        }
        log.info("外部认证成功: user={}", effectiveUser.getUsername());

        UsernamePasswordAuthenticationToken result = UsernamePasswordAuthenticationToken.authenticated(effectiveUser,
                authentication.getCredentials(), effectiveUser.getAuthorities());
        result.setDetails(authentication.getDetails());
        this.logger.debug("Authenticated user");
        return result;
    }
}